using System;
using System.Collections.Generic;
#if(!WindowsCE)
using System.Linq.Expressions;
using ValidationFramework.Reflection;
#endif

namespace ValidationFramework
{
    /// <summary>
    /// Provides an in memory cache of <see cref="TypeDescriptor"/>s.
    /// </summary>
    /// <example>
    /// <code source="Examples\ExampleLibraryCSharp\Reflection\AddWithTypeCacheExample.cs" title="The following example shows how to programmatically add validation Rules to a property." lang="cs" region="Example"/>
    /// <code source="Examples\ExampleLibraryVB\Reflection\AddWithTypeCacheExample.vb" title="The following example shows how to programmatically add validation Rules to a property." lang="vbnet" region="Example"/>
    /// <code source="Examples\ExampleLibraryCSharp\Reflection\TypeCacheExample.cs" title="The following example shows how to programmatically add validation Rules to a property." lang="cs" region="Example"/>
    /// <code source="Examples\ExampleLibraryVB\Reflection\TypeCacheExample.vb" title="The following example shows how to programmatically add validation Rules to a property." lang="vbnet" region="Example"/>
    /// <code source="Examples\ExampleLibraryCSharp\Reflection\AddCustomRuleWithTypeCacheExample.cs" lang="cs" title="This example shows how to programmatically add a CustomRule to a property." region="Example"/>
    /// <code source="Examples\ExampleLibraryCSharp\Reflection\AddCustomRuleWithTypeCacheStrongTypedExample.cs" lang="cs" title="This example shows how to programmatically add a CustomRule to a property." region="Example"/>
    /// <code source="Examples\ExampleLibraryVB\Reflection\AddCustomRuleWithTypeCacheExample.vb" lang="vbnet" title="This example shows how to programmatically add a CustomRule to a property." region="Example"/>
    /// </example>
    public class TypePolicyRepository
    {
        // --- Fields

        private IApplicationDesciber _appDesciber = null;
        private Dictionary<TypeKey, TypePolicy> _typePolicyDictionary;
        private IValidationPolicyFactory _vpFactory;

        // --- Properties
        public bool MulticastingOfRulesEnabled { get; set; }


        // --- Constructors
		internal TypePolicyRepository()
		{
			_vpFactory = new ValidationPolicyFactory(new ReflectionCache());
			_typePolicyDictionary = new Dictionary<TypeKey, TypePolicy>();
            _appDesciber = new AppDesciber();
            MulticastingOfRulesEnabled = true;
		}

		/// <summary>
		/// Initializes a new instance of the TypePolicyCache class.
		/// </summary>
		public TypePolicyRepository(IValidationPolicyFactory vpFactory, IApplicationDesciber AppDesciber)
		{
			_vpFactory = vpFactory;
            _appDesciber = AppDesciber;

            MulticastingOfRulesEnabled = true;
            _typePolicyDictionary = new Dictionary<TypeKey, TypePolicy>();
		}

		// --- Methods

		public TypePolicy GetOrCreateTypePolicy(TypeKey key)
        {
            //no need to check for null as RuntimeTypeHandle is a struct
            TypePolicy typePolicy;

			if (!_typePolicyDictionary.TryGetValue(key, out typePolicy))
            {
				typePolicy = new TypePolicy(_vpFactory);
				_typePolicyDictionary.Add(key, typePolicy);
            }
            return typePolicy;
        }


        public void AddPropertyRule(PropertyKey propertyKey, Rule rule, RuleScope scope)
		{
            // add rule to policy
			GetOrCreateTypePolicy(propertyKey.TypeKey)
                .GetOrCreatePropertyPolicy(propertyKey)
                .AddRule(rule, scope);

            if (MulticastingOfRulesEnabled)
            {
                var childTypes = _appDesciber.GetAllChildTypes(propertyKey.TypeKey.TargetType);
                foreach (var childType in childTypes)
                {
                    if (childType.ContainsProperty(propertyKey.PropertyName))
                    {
                        // add rule to child policy
                        var childPropertyKey = new PropertyKey(new TypeKey(childType), propertyKey.PropertyName);
                        GetOrCreateTypePolicy(childPropertyKey.TypeKey)
                            .GetOrCreatePropertyPolicy(childPropertyKey)
                            .AddRule(rule, scope);
                    }
                }

            }
		}


		public void AddFieldRule(FieldKey fieldKey, Rule rule, RuleScope scope)
		{
			GetOrCreateTypePolicy(fieldKey.TypeKey)
				.GetOrCreateFieldPolicy(fieldKey)
				.AddRule(rule, scope);
		}

        public void Clear()
        {
            _typePolicyDictionary.Clear();
        }

		public IList<IValidationPolicy> GetPropertyPoliciesForType(TypeKey key)
		{
			var policy = this.GetOrCreateTypePolicy(key);

			var properyPolicies = new List<IValidationPolicy>();
			foreach (var kvp in policy.PropertyPolicyDictionary)
			{
				properyPolicies.Add(kvp.Value);
			}
			return properyPolicies;
		}
        public IValidationPolicy GetPropertyPolicy(PropertyKey propertyKey)
        {
			var typePolicy = GetOrCreateTypePolicy(propertyKey.TypeKey);
			return typePolicy.GetOrCreatePropertyPolicy(propertyKey);
        }

		public IList<IValidationPolicy> GetFieldPoliciesForType(TypeKey key)
		{
			var policy = this.GetOrCreateTypePolicy(key);

			var fieldPolicies = new List<IValidationPolicy>();
			foreach (var kvp in policy.FieldPolicyDictionary)
			{
				fieldPolicies.Add(kvp.Value);
			}
			return fieldPolicies;
		}
		public IValidationPolicy GetFieldPolicy(FieldKey fieldKey)
		{
			var typePolicy = GetOrCreateTypePolicy(fieldKey.TypeKey);
			return typePolicy.GetOrCreateFieldPolicy(fieldKey);
		}
    }
}